home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Source Code / C / Applications / Python 1.3.3 / Python 133 PPC / Mac / Lib / dbmac.py < prev    next >
Text File  |  1996-05-19  |  3KB  |  141 lines

  1. """A slow but simple dbm clone for the Mac.
  2.  
  3. For database spam, spam.dir contains the index (a text file),
  4. spam.bak *may* contain a backup of the index (also a text file),
  5. while spam.dat contains the data (a binary file).
  6.  
  7. XXX TO DO:
  8.  
  9. - reclaim free space (currently, space once occupied by deleted or expanded
  10. items is never reused)
  11.  
  12. - support concurrent access (currently, if two processes take turns making
  13. updates, they can mess up the index)
  14.  
  15. - support efficient access to large databases (currently, the whole index
  16. is read when the database is opened, and some updates rewrite the whole index)
  17.  
  18. - support opening for read-only (flag = 'm')
  19.  
  20. """
  21.  
  22. _os = __import__('os')
  23. import __builtin__
  24.  
  25. _open = __builtin__.open
  26.  
  27. _BLOCKSIZE = 512
  28.  
  29. class _Database:
  30.  
  31.     def __init__(self, file):
  32.         self._dirfile = file + '.dir'
  33.         self._datfile = file + '.dat'
  34.         self._bakfile = file + '.bak'
  35.         # Mod by Jack: create data file if needed
  36.         try:
  37.             f = _open(self._datfile, 'r')
  38.         except IOError:
  39.             f = _open(self._datfile, 'w')
  40.         f.close()
  41.         self._update()
  42.     
  43.     def _update(self):
  44.         self._index = {}
  45.         try:
  46.             f = _open(self._dirfile)
  47.         except IOError:
  48.             pass
  49.         else:
  50.             while 1:
  51.                 line = f.readline()
  52.                 if not line: break
  53.                 key, (pos, siz) = eval(line)
  54.                 self._index[key] = (pos, siz)
  55.             f.close()
  56.  
  57.     def _commit(self):
  58.         try: _os.unlink(self._bakfile)
  59.         except _os.error: pass
  60.         try: _os.rename(self._dirfile, self._bakfile)
  61.         except _os.error: pass
  62.         f = _open(self._dirfile, 'w')
  63.         for key, (pos, siz) in self._index.items():
  64.             f.write("%s, (%s, %s)\n" % (`key`, `pos`, `siz`))
  65.         f.close()
  66.     
  67.     def __getitem__(self, key):
  68.         pos, siz = self._index[key]    # may raise KeyError
  69.         f = _open(self._datfile, 'rb')
  70.         f.seek(pos)
  71.         dat = f.read(siz)
  72.         f.close()
  73.         return dat
  74.     
  75.     def _addval(self, val):
  76.         f = _open(self._datfile, 'rb+')
  77.         f.seek(0, 2)
  78.         pos = f.tell()
  79. ## Does not work under MW compiler
  80. ##        pos = ((pos + _BLOCKSIZE - 1) / _BLOCKSIZE) * _BLOCKSIZE
  81. ##        f.seek(pos)
  82.         npos = ((pos + _BLOCKSIZE - 1) / _BLOCKSIZE) * _BLOCKSIZE
  83.         f.write('\0'*(npos-pos))
  84.         pos = npos
  85.         
  86.         f.write(val)
  87.         f.close()
  88.         return (pos, len(val))
  89.     
  90.     def _setval(self, pos, val):
  91.         f = _open(self._datfile, 'rb+')
  92.         f.seek(pos)
  93.         f.write(val)
  94.         f.close()
  95.         return pos, (val)
  96.     
  97.     def _addkey(self, key, (pos, siz)):
  98.         self._index[key] = (pos, siz)
  99.         f = _open(self._dirfile, 'a')
  100.         f.write("%s, (%s, %s)\n" % (`key`, `pos`, `siz`))
  101.         f.close()
  102.     
  103.     def __setitem__(self, key, val):
  104.         if not type(key) == type('') == type(val):
  105.             raise TypeError, "dbmac keys and values must be strings"
  106.         if not self._index.has_key(key):
  107.             (pos, siz) = self._addval(val)
  108.             self._addkey(key, (pos, siz))
  109.         else:
  110.             pos, siz = self._index[key]
  111.             oldblocks = (siz + _BLOCKSIZE - 1) / _BLOCKSIZE
  112.             newblocks = (len(val) + _BLOCKSIZE - 1) / _BLOCKSIZE
  113.             if newblocks <= oldblocks:
  114.                 pos, siz = self._setval(pos, val)
  115.                 self._index[key] = pos, siz
  116.             else:
  117.                 pos, siz = self._addval(val)
  118.                 self._index[key] = pos, siz
  119.             self._addkey(key, (pos, siz))
  120.     
  121.     def __delitem__(self, key):
  122.         del self._index[key]
  123.         self._commit()
  124.     
  125.     def keys(self):
  126.         return self._index.keys()
  127.     
  128.     def has_key(self, key):
  129.         return self._index.has_key(key)
  130.     
  131.     def __len__(self):
  132.         return len(self._index)
  133.     
  134.     def close(self):
  135.         self._index = self._datfile = self._dirfile = self._bakfile = None
  136.  
  137.  
  138. def open(file, flag = None, mode = None):
  139.     # flag, mode arguments are currently ignored
  140.     return _Database(file)
  141.